django4.2.3 + python3.10 + win11 + openpyxl3.1.2
本篇博客主要演示了,使用Django完成对本地Excel表格的上传下载操作,当然,其他类型的文件也一样。
文件上传基于form表单上传urls.py:
from django.contrib import adminfrom django.urls import pathfrom api import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index),]models.py:
from django.db import modelsclass User(models.Model):name = models.CharField(max_length=64, default='')def __str__(self):return self.nameindex.html:
index{% csrf_token %}{{ error }}一般的,在普通的form表单提交时,请求头中的CONTENT_TYPE: application/x-www-form-urlencoded,然后数据是以键值对的形式传输的,服务端从request.POST取值,这没问题,并且CONTENT_TYPE: application/x-www-form-urlencoded这种编码类型满足大多数情况,一切都挺好的。
而要说使用form表单上传文件,就不得不多说两句了。起初,http 协议中没有上传文件方面的功能,直到 rfc1867 为 http 协议添加了这个功能。当然在 rfc1867 中限定 form标签的 method 必须为 POST,enctype = "multipart/form-data" 以及。所以,当使用form表单上传文件的时候,请求头的content_type是multipart/form-data这种形式的,所以,我们需要在form标签添加enctype="multipart/form-data属性来进行标识。如果你能打印上传文件的请求头,你会发现CONTENT_TYPE是这样的content_type:multipart/form-data; boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU,那boundary=----WebKitFormBoundarylZZyJUkrgm6h34DU又是什么呢?在multipart/form-data 后面有boundary以及一串字符,这是分界符,后面的一堆字符串是随机生成的,目的是防止上传文件中出现分界符导致服务器无法正确识别文件起始位置。那分界符又有啥用呢?对于上传文件的post请求,我们没有使用原有的 http 协议,所以 multipart/form-data 请求是基于 http 原有的请求方式 post 而来的,那么来说说这个全新的请求方式与 post 的区别:
请求头的不同,对于上传文件的请求,contentType = multipart/form-data是必须的,而 post 则不是,毕竟 post 又不是只上传文件~。请求体不同,这里的不同也就是指前者(上传文件请求)在发送的每个字段内容之间必须要使用分界符来隔开,比如文件的内容和文本的内容就需要分隔开,不然服务器就没有办法正常的解析文件,而后者 post 当然就没有分界符直接以key:value的形式发送就可以了。当然,其中的弯弯绕绕不是三言两语能解释的清楚的,我们先知道怎么用就行。
views.py:
import osfrom openpyxl import load_workbookfrom django.shortcuts import render, redirect, HttpResponsefrom django.db import transactionfrom api.models import Userdef index(request):""" 导入Excel数据 """if request.method == 'POST':try:with transaction.atomic():# 事物# post请求,我们仍然可以正常提交其他数据user = request.POST.get("user")# print(user)excel = request.FILES.get('f1')"""# 先写到本地也行path = os.path.join("media", "files", excel.name)with open(path, 'wb') as f:for line in excel:f.write(line)wb = load_workbook(path)"""# 直接openpyxl直接load也行wb = load_workbook(excel)sheet = wb.worksheets[0]for row in sheet.iter_rows():# 获取每一行的内容# print(row[0].value)# 这里取出来每行的数据,打印如果是乱码,不要怕,正常写入数据就没问题的User.objects.create(name=row[0].value)return HttpResponse('OK')except Exception as e:print(e)return render(request, 'index.html', {"error": "上传文件类型有误,只支持 xls 和 xlsx 格式的 Excel文档"})return render(request, 'index.html', {"error": ""})基于Ajax上传urls.py:
from django.contrib import adminfrom django.urls import pathfrom api import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index),path('upload/', views.upload),]models.py:
from django.db import modelsclass User(models.Model):name = models.CharField(max_length=64, default='')def __str__(self):return self.nameindex.html:
Title{% csrf_token %}上传{{ error }}console.log($("[name='csrfmiddlewaretoken']").val());$("#ajaxBtn").click(function () {// 首先,实例化一个formdata对象var formData = new FormData();// 然后使用formdata的append来添加数据,即获取文件对象// var file_obj = $("#ajaxFile")[0].files[0];// 使用jQuery获取文件对象var file_obj = document.getElementById('ajaxFile').files[0];// 使用dom也行formData.append('f1', file_obj );// 处理csrftokenformData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());// 也可以将其他的数据,以键值对的形式,添加到formData中formData.append('user',$("#user").val());$.ajax({url: "/upload/",type: "POST",data: formData,processData:false, // 默认情况下会将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded,如果发送不想转换的的信息的时候需要手动将其设置为falsecontentType:false, // 固定写法success:function (dataMsg) {console.log(dataMsg);$("#msg").text(dataMsg['message'])}})})在 ajax 中 contentType 设置为 false 是为了避免 JQuery 对请求头content_type进行操作,从而失去分界符,而使服务器不能正常解析文件。在使用jQuery的$.ajax()方法的时候参数processData默认为true(该方法为jQuery独有的),默认情况下会将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded如果想发送不想转换的信息的时候需要手动将其设置为false即可。
views.py:
from openpyxl import load_workbookfrom django.shortcuts import render, redirect, HttpResponsefrom django.http import JsonResponsefrom django.db import transactionfrom api.models import Userdef upload(request):if request.method == 'POST':user = request.POST.get("user")print(user)f1 = request.FILES.get('f1')wb = load_workbook(f1)sheet = wb["北京"]for row in sheet.iter_rows():# 获取每一行的内容print(row[0].value)# 这里取出来每行的数据,打印如果是乱码,不要怕,正常写入数据就没问题的User.objects.create(name=row[0].value)return JsonResponse({"message": "upload successful"})else:return render(request, 'upload.html')文件下载文件下载这里主要就是从前端获取要下载的文件。
这个文件可以是从数据库中读出来的,临时写入到服务器某个文件夹内,或者文件就在服务器上存着,这都无所谓。主要就是open读这个文件,然后让浏览器帮我们下载就行了。
文件下载通过两个类来完成:
from django.http import StreamingHttpResponsefrom django.http import FileResponse # 是StreamingHttpResponse的子类,都是流式下载,用哪个都行from django.utils.encoding import escape_uri_path# 导入这个家伙防止中文文件名乱码或者打不开来看示例。
urls.py:
from django.contrib import adminfrom django.urls import pathfrom api import viewsurlpatterns = [path('admin/', admin.site.urls),path('index/', views.index),path('upload/', views.upload),path('download1/', views.download1),path('download2/', views.download2),path('users/', views.users),]models.py:
from django.db import modelsclass User(models.Model):name = models.CharField(max_length=64, default='')def __str__(self):return self.nameuser_list.html:
Title点我下载点我下载重点在views.py:
import osfrom openpyxl import load_workbookfrom openpyxl.workbook import Workbookfrom django.shortcuts import render, redirect, HttpResponsefrom django.http import StreamingHttpResponse, FileResponsefrom django.utils.encoding import escape_uri_path# 导入这个家伙from django.http import JsonResponsefrom django.db import transactionfrom api.models import Userdef create_file(data):filename = '学生表.xlsx'file_path = os.path.join("media", "files", filename)wb = Workbook()sheet = wb['Sheet']for index, user in enumerate(data, 1):cell = sheet[f'A{index}']cell.value = user.namewb.save(file_path)return file_path, filenamedef users(request):user_list = User.objects.all()return render(request, 'user_list.html', {"user_list": user_list})def download1(request):user_list = User.objects.all()file_path, filename = create_file(user_list)file = open(file_path, 'rb')response = StreamingHttpResponse(file)response['Content-Type'] = 'application/octet-stream'# escape_uri_path中填写路径,防止中文文件名下载后乱码或者无法打开response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(filename))return responsedef download2(request):user_list = User.objects.all()file_path, filename = create_file(user_list)file = open(file_path, 'rb')response = FileResponse(file)response['Content-Type'] = 'application/octet-stream'# escape_uri_path中填写路径,防止中文文件名下载后乱码或者无法打开response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(filename))return response